using System;
using System.Collections.Generic;
using System.Linq;

namespace MathSharp.Script
{
/*
 * Описание                                | Тип          | Релизация
 * ----------------------------------------+--------------+-----------
 * число                                   | Int          | +
 * число с плавоющей точкой                | Double       | +
 * строка                                  | String       | +
 * логическое                              | Bool         | +
 * масив                                   | Array        |
 * пара ключ-значение                      | KeyValuePair |
 * словарь                                 | Dictionary   |
 * куб (многомерная структура)             | Cube         |
 * Null значение                           | Null         | +
 * не определённое значение                | Variable     |
 * Пустое значение для возврата из функций | Void         |
 */
	
	public abstract class Value {
		public Namespace Namepsace {
			get;
			protected set;
		}
		
		public Script Script {
			get {
				return Namepsace.Script;
			}
		}
		
		public Value(Namespace ns) {
			this.Namepsace = ns;
		}
		
		public abstract ValueType Type {
			get;
		}
		
		public abstract Value GetSubValue(params Value[] dimensions);
		
		//public abstract void SetSubValue(Value val, params ScalarValue[] dimensions);
		
		
		public Value MakeMathUnaryOperation(string operation) {
			DeclareOperatorCommand cmd = Namepsace.TryGetOperation(operation, this, null, null);
			if (cmd == null)
				throw new ExecuteException(Script.FileName, 0, 0, 0x0,
				                           "Не удалось найти найти реализацию операции {0} для операторов типа {1} и {2}",
				                           operation, this.Type, secondOperand.Type);
			return cmd.ExecuteDeclaration(new Value[] { this });
		}
		
		public Value MakeMathBinaryOperation(string operation, Value secondOperand) {
			DeclareOperatorCommand cmd = Namepsace.TryGetOperation(operation, this, secondOperand, null);
			if (cmd == null)
				throw new ExecuteException(Script.FileName, 0, 0, 0x0,
				                          "Не удалось найти найти реализацию операции {0} для операторов типа {1} и {2}",
				                          operation, this.Type, secondOperand.Type);
			return cmd.ExecuteDeclaration(new Value[] { this, secondOperand });
		}
		
		
		public Value MakeMathTernaryOperation(string operation, Value secondOperand, Value threeOperand) {
			DeclareOperatorCommand cmd = Namepsace.TryGetOperation(operation, this, secondOperand, threeOperand);
			if (cmd == null)
				throw new ExecuteException(Script.FileName, 0, 0, 0x0,
				                          "Не удалось найти найти реализацию операции {0} для операторов типа {1} и {2}",
				                          operation, this.Type, secondOperand.Type);
			return cmd.ExecuteDeclaration(new Value[] { this, secondOperand, threeOperand });
		}
		
		public static Value CreateFromNativeValue(object val, Script script) {
			Type t = val.GetType();
			if (t.IsAnyNumeric() || t.IsAnyFloatPointNumber() ||
			    t == typeof(string) || t == typeof(bool)) {
				return ScalarValue<object>.CreateFromNativeValue(val, script);
			} else
				throw new ExecuteException(script.FileName, 0, 0, 0x0,
				                           "Невозможно создать значение из нативного типа: непоодерживаемый нативный тип {0}", t.FullName);
			    
		}
	}
	
	public interface IScalarValue {
		int GetIntValue();
		double GetDoubleValue();
		string GetStringValue();
		bool GetBoolValue();
	}
	
	public abstract class ScalarValue<T>: Value {
		protected T val;
		
		public ScalarValue(Script script): base(script) {
		}
		
		public static Value CreateFromNativeValue(object val, Script script) {
			if (val == null ||
			    (val is string && (val as string) == "null"))
				return new NullValue(script);
			Type t = val.GetType();
			Value res;
			if (t.IsAnyInteger()) {
				res = new IntValue(script) { val = (int)val };
			} else if (t.IsAnyFloatPointNumber()) {
				res = new DoubleValue(script) { val = (double)val };
			} else if (t == typeof(bool)) {
				res = new BoolValue(script) { val = (bool)val };
			} else if (t == typeof(string)) {
				res = new StringValue(script) { val = (string)val };
			} else 
				throw new ExecuteException(script.FileName, 0, 0, 0x0,
				                           "Не удалось создать ScalarValue на основе нативного типа {0} т.к. он не поддерживается", t.FullName);
			return res;
		}
		
		public sealed override Value GetSubValue (params Value[] dimensions)
		{
			throw new ExecuteException(Script.FileName, 0, 0, 0x0, "Невозможно у скалярного типа {0} получить подзначение", Type);
		}

		//public override void SetSubValue (Value val, params ScalarValue[] dimensions)
		//{
		//	throw new ExecuteException(Script.FileName, 0, 0, 0x0, "Невозможно скалярному типу {0} установить подзначение", Type);
		//}
		
		public void SetValue(object newVal) {
			Type t = typeof(T);
			if (newVal == null && Type != ValueType.Null)
				throw new ArgumentException("Невозможно назначить null значению не Null-типа");
			if (newVal is Value) { // новое значение описанно классом значения
				ValueType myVType = this.Type;
				if (newVal is IScalarValue) {
					IScalarValue sV = (val as IScalarValue);
					switch (myVType) {
						case ValueType.Int:
							SetValue(sV.GetIntValue());
							break;
						case ValueType.Double:
							SetValue(sV.GetDoubleValue());
							break;
						case ValueType.String:
							SetValue(sV.GetStringValue());
							break;
						case ValueType.Bool:
							SetValue(sV.GetBoolValue());
							break;
						case ValueType.Null:
							// null не может менять своего значения
							break;
						default:
							throw new ArgumentException("Неизвестный тип значения относящийся к ScalarValue: {0}".format(myVType));
					}
				} else {
					throw new NotImplementedException("Обработка назначения значения ScalarValue от нескалярных типов не реализованна");
				}
			} else if (newVal is Variable) { // новое значение описанно переменной
				SetValue((newVal as Variable).Value);
			} else { // новое значение описанно нативным значением
				switch (Type) {
					case ValueType.Int: 
						val = (T)(object)Convert.ToInt32(newVal);
						break;
					case ValueType.Double:
						val = (T)(object)Convert.ToDouble(newVal);
						break;
					case ValueType.Bool:
						val = (T)(object)Convert.ToBoolean(newVal);
						break;
					case ValueType.String:
						val = (T)(object)Convert.ToString(newVal);
						break;
					default:
						throw new Exception("При назначении значения встретился неизвестный тип значения {0}".format(Type));
				}
			}
		}
	}
	
	public class IntValue: ScalarValue<int>, IScalarValue {		
		public IntValue(Script script): base(script) {
		}
		
		public IntValue(Script script, int val): this(script) {
			this.val = val;
		}
		
		public override ValueType Type {
			get {
				return ValueType.Int;
			}
		}

		public int GetIntValue ()
		{
			return val;
		}

		public double GetDoubleValue ()
		{
			return val;
		}

		public bool GetBoolValue ()
		{
			return val != 0;
		}

		public string GetStringValue ()
		{
			return val.ToString();
		}		
	}
	
	public class DoubleValue: ScalarValue<double>, IScalarValue {
		public DoubleValue(Script script): base(script) {
		}
		
		public DoubleValue(Script script, double val): this(script) {
			this.val = val;
		}
		
		public override ValueType Type {
			get {
				return ValueType.Double;
			}
		}

		public int GetIntValue ()
		{
			return (int)Math.Round(val, 0);
		}

		public double GetDoubleValue ()
		{
			return val;
		}

		public bool GetBoolValue ()
		{
			return val != 0;
		}

		public string GetStringValue ()
		{
			return val.ToString();
		}		
	}
	
	public class StringValue: ScalarValue<string>, IScalarValue {
		public StringValue(Script script): base(script) {
		}
		
		public StringValue(Script script, string val): this(script) {
			this.val = val;
		}
		
		public override ValueType Type {
			get {
				return ValueType.String;
			}
		}

		public int GetIntValue ()
		{
			int res;
			if (int.TryParse(val, out res))
				return res;
			else
				throw new ExecuteException(Script.FileName, 0, 0, 0x0, "Невозможно привести строковое значение \"{0}\" к числовому", val);
		}

		public double GetDoubleValue ()
		{
			double res;
			if (double.TryParse(val, out res))
				return res;
			else
				throw new ExecuteException(Script.FileName, 0, 0, 0x0, "Невозможно привести строковое значение \"{0}\" к числу с плавующей точкой", val);
		}
		
		readonly string[] trueStrings  = new string[] { "yes", "1", "true",  "да",  "истина" };
		readonly string[] falseStrings = new string[] { "no",  "0", "false", "нет", "ложь" };

		public bool GetBoolValue ()
		{
			if (trueStrings.Contains(val.ToLower()))
				return true;
			if (falseStrings.Contains(val.ToLower()))
				return false;			    
			throw new ExecuteException(Script.FileName, 0, 0, 0x0, "Невозможно привести строковое значение \"{0}\" к логичемкому", val);
		}

		public string GetStringValue ()
		{
			return val;
		}		
	}
	
	public class BoolValue: ScalarValue<bool>, IScalarValue {
		public BoolValue(Script script): base(script) {
		}
		
		public BoolValue(Script script, bool val): this(script) {
			this.val = val;
		}
		
		public override ValueType Type {
			get {
				return ValueType.Bool;
			}
		}

		public int GetIntValue ()
		{
			return val?1:0;
		}

		public double GetDoubleValue ()
		{
			return val?1:0;
		}

		public bool GetBoolValue ()
		{
			return val;
		}

		public string GetStringValue ()
		{
			return val?bool.TrueString:bool.FalseString;
		}
	}
	
	public class NullValue: ScalarValue<object>, IScalarValue {
		public NullValue(Script script): base(script) {
		}
		
		public override ValueType Type {
			get {
				return ValueType.Null;
			}
		}

		public int GetIntValue ()
		{
			return 0;
		}

		public double GetDoubleValue ()
		{
			return 0;
		}

		public bool GetBoolValue ()
		{
			return false;
		}

		public string GetStringValue ()
		{
			return null;
		}
	}
}
